/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2017 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockAreaWidget.h"
#include "QxDockAreaTabBar.h"
#include "QxDockAreaTitleBar.h"
#include "QxDockFactory.h"
#include "QxDockContainerWidget.h"
#include "QxDockManager.h"
#include "QxDockOverlay.h"
#include "QxDockSplitter.h"
#include "QxDockWidget.h"
#include "QxDockWidgetTab.h"
#include "QxDockStateReader.h"
#include "QxDockElidingLabel.h"
#include "QxDockFloatingContainer.h"
#include "QxDockAutoHideContainer.h"
#include "QxDockAutoHideTab.h"

#include <QDebug>
#include <QList>
#include <QMenu>
#include <QPushButton>
#include <QScrollBar>
#include <QStackedLayout>
#include <QStyle>
#include <QWheelEvent>
#include <QXmlStreamWriter>

QX_BEGIN_NAMESPACE

static const char *const INDEX_PROPERTY = "index";
static const char *const ACTION_PROPERTY = "action";

/**
 * Check, if auto hide is enabled
 */
static bool isAutoHideFeatureEnabled()
{
    return DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled);
}

/**
 * Internal dock area layout mimics stack layout but only inserts the current
 * widget into the internal QLayout object.
 * \warning Only the current widget has a parent. All other widgets
 * do not have a parent. That means, a widget that is in this layout may
 * return nullptr for its parent() function if it is not the current widget.
 */
class CDockAreaLayout
{
private:
    QBoxLayout *m_ParentLayout;
    QList<QWidget *> m_Widgets;
    int m_CurrentIndex = -1;
    QWidget *m_CurrentWidget = nullptr;
public:
    /**
     * Creates an instance with the given parent layout
     */
    CDockAreaLayout(QBoxLayout *ParentLayout) : m_ParentLayout(ParentLayout)
    {
    }

    /**
     * Returns the number of widgets in this layout
     */
    int count() const
    {
        return m_Widgets.count();
    }

    /**
     * Inserts the widget at the given index position into the internal widget
     * list
     */
    void insertWidget(int index, QWidget *Widget)
    {
        Widget->setParent(nullptr);
        if (index < 0) {
            index = m_Widgets.count();
        }
        m_Widgets.insert(index, Widget);
        if (m_CurrentIndex < 0) {
            setCurrentIndex(index);
        } else {
            if (index <= m_CurrentIndex) {
                ++m_CurrentIndex;
            }
        }
    }

    /**
     * Removes the given widget from the layout
     */
    void removeWidget(QWidget *Widget)
    {
        if (currentWidget() == Widget) {
            auto LayoutItem = m_ParentLayout->takeAt(1);
            if (LayoutItem) {
                LayoutItem->widget()->setParent(nullptr);
            }
            m_CurrentWidget = nullptr;
            m_CurrentIndex = -1;
        } else if (indexOf(Widget) < m_CurrentIndex) {
            --m_CurrentIndex;
        }
        m_Widgets.removeOne(Widget);
    }

    /**
     * Returns the current selected widget
     */
    QWidget *currentWidget() const
    {
        return m_CurrentWidget;
    }

    /**
     * Activates the widget with the give index.
     */
    void setCurrentIndex(int index)
    {
        QWidget *prev = currentWidget();
        QWidget *next = widget(index);
        if (!next || (next == prev && !m_CurrentWidget)) {
            return;
        }

        bool reenableUpdates = false;
        QWidget *parent = m_ParentLayout->parentWidget();

        if (parent && parent->updatesEnabled()) {
            reenableUpdates = true;
            parent->setUpdatesEnabled(false);
        }

        auto LayoutItem = m_ParentLayout->takeAt(1);
        if (LayoutItem) {
            LayoutItem->widget()->setParent(nullptr);
        }
        delete LayoutItem;

        m_ParentLayout->addWidget(next);
        if (prev) {
            prev->hide();
        }
        m_CurrentIndex = index;
        m_CurrentWidget = next;

        if (reenableUpdates) {
            parent->setUpdatesEnabled(true);
        }
    }

    /**
     * Returns the index of the current active widget
     */
    int currentIndex() const
    {
        return m_CurrentIndex;
    }

    /**
     * Returns true if there are no widgets in the layout
     */
    bool isEmpty() const
    {
        return m_Widgets.empty();
    }

    /**
     * Returns the index of the given widget
     */
    int indexOf(QWidget *w) const
    {
        return m_Widgets.indexOf(w);
    }

    /**
     * Returns the widget for the given index
     */
    QWidget *widget(int index) const
    {
        return (index < m_Widgets.size()) ? m_Widgets.at(index) : nullptr;
    }

    /**
     * Returns the geometry of the current active widget
     */
    QRect geometry() const
    {
        return m_Widgets.empty() ? QRect() : currentWidget()->geometry();
    }
};

using DockAreaLayout = CDockAreaLayout;
static const DockWidgetAreas DefaultAllowedAreas = AllDockAreas;

/**
 * Private data class of DockAreaWidget class (pimpl)
 */
struct DockAreaWidgetPrivate {
    DockAreaWidget *_this = nullptr;
    QBoxLayout *Layout = nullptr;
    DockAreaLayout *ContentsLayout = nullptr;
    DockAreaTitleBar *TitleBar = nullptr;
    DockManager *dockManager = nullptr;
    DockAutoHideContainer *AutoHideDockContainer = nullptr;
    bool UpdateTitleBarButtons = false;
    DockWidgetAreas AllowedAreas = DefaultAllowedAreas;
    QSize MinSizeHint;
    DockAreaWidget::DockAreaFlags Flags{DockAreaWidget::DefaultFlags};

    /**
     * Private data constructor
     */
    DockAreaWidgetPrivate(DockAreaWidget *_public);

    /**
     * Creates the layout for top area with tabs and close button
     */
    void createTitleBar();

    /**
     * Returns the dock widget with the given index
     */
    DockWidget *dockWidgetAt(int index)
    {
        return qobject_cast<DockWidget *>(ContentsLayout->widget(index));
    }

    /**
     * Convenience function to ease title widget access by index
     */
    DockWidgetTab *tabWidgetAt(int index)
    {
        return dockWidgetAt(index)->tabWidget();
    }

    /**
     * Returns the tab action of the given dock widget
     */
    QAction *dockWidgetTabAction(DockWidget *dockWidget) const
    {
        return qvariant_cast<QAction *>(dockWidget->property(ACTION_PROPERTY));
    }

    /**
     * Returns the index of the given dock widget
     */
    int dockWidgetIndex(DockWidget *dockWidget) const
    {
        return dockWidget->property(INDEX_PROPERTY).toInt();
    }

    /**
     * Convenience function for tabbar access
     */
    DockAreaTabBar *tabBar() const
    {
        return TitleBar->tabBar();
    }

    /**
     * Udpates the enable state of the close and detach button
     */
    void updateTitleBarButtonStates();

    /**
     * Udpates the enable state of the close and detach button
     */
    void updateTitleBarButtonVisibility(bool isTopLevel);

    /**
     * Scans all contained dock widgets for the max. minimum size hint
     */
    void updateMinimumSizeHint()
    {
        MinSizeHint = QSize();
        for (int i = 0; i < ContentsLayout->count(); ++i) {
            auto Widget = ContentsLayout->widget(i);
            MinSizeHint.setHeight(qMax(MinSizeHint.height(), Widget->minimumSizeHint().height()));
            MinSizeHint.setWidth(qMax(MinSizeHint.width(), Widget->minimumSizeHint().width()));
        }
    }
};

DockAreaWidgetPrivate::DockAreaWidgetPrivate(DockAreaWidget *_public) : _this(_public)
{
}

void DockAreaWidgetPrivate::createTitleBar()
{
    TitleBar = componentsFactory()->createDockAreaTitleBar(_this);
    Layout->addWidget(TitleBar);
    QObject::connect(tabBar(), &DockAreaTabBar::tabCloseRequested, _this, &DockAreaWidget::onTabCloseRequested);
    QObject::connect(TitleBar, &DockAreaTitleBar::tabBarClicked, _this, &DockAreaWidget::setCurrentIndex);
    QObject::connect(tabBar(), &DockAreaTabBar::tabMoved, _this, &DockAreaWidget::reorderDockWidget);
}

void DockAreaWidgetPrivate::updateTitleBarButtonStates()
{
    if (_this->isHidden()) {
        UpdateTitleBarButtons = true;
        return;
    }

    TitleBar->button(TitleBarButtonClose)->setEnabled(_this->features().testFlag(DockWidget::DockWidgetClosable));
    TitleBar->button(TitleBarButtonUndock)->setEnabled(_this->features().testFlag(DockWidget::DockWidgetFloatable));
    TitleBar->button(TitleBarButtonAutoHide)->setEnabled(_this->features().testFlag(DockWidget::DockWidgetPinnable));
    TitleBar->updateDockWidgetActionsButtons();
    UpdateTitleBarButtons = false;
}

void DockAreaWidgetPrivate::updateTitleBarButtonVisibility(bool IsTopLevel)
{
    auto *const container = _this->dockContainer();
    if (!container) {
        return;
    }

    if (IsTopLevel) {
        TitleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating());
        TitleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating());
        // Undock and tabs should never show when auto hidden
        TitleBar->button(TitleBarButtonUndock)->setVisible(!container->isFloating() && !_this->isAutoHide());
        TitleBar->button(TitleBarButtonTabsMenu)->setVisible(!_this->isAutoHide());
    } else {
        TitleBar->button(TitleBarButtonClose)->setVisible(true);
        TitleBar->button(TitleBarButtonAutoHide)->setVisible(true);
        TitleBar->button(TitleBarButtonUndock)->setVisible(!_this->isAutoHide());
        TitleBar->button(TitleBarButtonTabsMenu)->setVisible(!_this->isAutoHide());
    }
}

DockAreaWidget::DockAreaWidget(DockManager *dockManager, DockContainerWidget *parent)
    : QFrame(parent), d(new DockAreaWidgetPrivate(this))
{
    d->dockManager = dockManager;
    d->Layout = new QBoxLayout(QBoxLayout::TopToBottom);
    d->Layout->setContentsMargins(0, 0, 0, 0);
    d->Layout->setSpacing(0);
    setLayout(d->Layout);

    d->createTitleBar();
    d->ContentsLayout = new DockAreaLayout(d->Layout);
    if (d->dockManager) {
        Q_EMIT d->dockManager->dockAreaCreated(this);
    }
}

DockAreaWidget::~DockAreaWidget()
{
    QX_DOCK_PRINT("~DockAreaWidget()");
    delete d->ContentsLayout;
    delete d;
}

DockManager *DockAreaWidget::dockManager() const
{
    return d->dockManager;
}

DockContainerWidget *DockAreaWidget::dockContainer() const
{
    return internal::findParent<DockContainerWidget *>(this);
}

DockAutoHideContainer *DockAreaWidget::autoHideDockContainer() const
{
    return d->AutoHideDockContainer;
}

bool DockAreaWidget::isAutoHide() const
{
    return d->AutoHideDockContainer != nullptr;
}

void DockAreaWidget::setAutoHideDockContainer(DockAutoHideContainer *AutoHideDockContainer)
{
    d->AutoHideDockContainer = AutoHideDockContainer;
    updateAutoHideButtonCheckState();
    updateTitleBarButtonsToolTips();
}

void DockAreaWidget::addDockWidget(DockWidget *dockWidget)
{
    insertDockWidget(d->ContentsLayout->count(), dockWidget);
}

void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool Activate)
{
    if (index < 0 || index > d->ContentsLayout->count()) {
        index = d->ContentsLayout->count();
    }
    d->ContentsLayout->insertWidget(index, dockWidget);
    dockWidget->setDockArea(this);
    dockWidget->tabWidget()->setDockAreaWidget(this);
    auto TabWidget = dockWidget->tabWidget();
    // Inserting the tab will change the current index which in turn will
    // make the tab widget visible in the slot
    d->tabBar()->blockSignals(true);
    d->tabBar()->insertTab(index, TabWidget);
    d->tabBar()->blockSignals(false);
    TabWidget->setVisible(!dockWidget->isClosed());
    d->TitleBar->autoHideTitleLabel()->setText(dockWidget->windowTitle());
    dockWidget->setProperty(INDEX_PROPERTY, index);
    d->MinSizeHint.setHeight(qMax(d->MinSizeHint.height(), dockWidget->minimumSizeHint().height()));
    d->MinSizeHint.setWidth(qMax(d->MinSizeHint.width(), dockWidget->minimumSizeHint().width()));
    if (Activate) {
        setCurrentIndex(index);
        dockWidget->setClosedState(false);   // Set current index can show the widget without changing the close state,
                                             // added to keep the close state consistent
    }
    // If this dock area is hidden, then we need to make it visible again
    // by calling dockWidget->toggleViewInternal(true);
    if (!this->isVisible() && d->ContentsLayout->count() > 1 && !dockManager()->isRestoringState()) {
        dockWidget->toggleViewInternal(true);
    }
    d->updateTitleBarButtonStates();
    updateTitleBarVisibility();
}

void DockAreaWidget::removeDockWidget(DockWidget *dockWidget)
{
    QX_DOCK_PRINT("DockAreaWidget::removeDockWidget");
    if (!dockWidget) {
        return;
    }

    // If this dock area is in a auto hide container, then we can delete
    // the auto hide container now
    if (isAutoHide()) {
        autoHideDockContainer()->cleanupAndDelete();
        return;
    }

    auto CurrentDockWidget = currentDockWidget();
    auto NextOpenDockWidget = (dockWidget == CurrentDockWidget) ? nextOpenDockWidget(dockWidget) : nullptr;

    d->ContentsLayout->removeWidget(dockWidget);
    auto TabWidget = dockWidget->tabWidget();
    TabWidget->hide();
    d->tabBar()->removeTab(TabWidget);
    TabWidget->setParent(dockWidget);
    dockWidget->setDockArea(nullptr);
    DockContainerWidget *DockContainer = dockContainer();
    if (NextOpenDockWidget) {
        setCurrentDockWidget(NextOpenDockWidget);
    } else if (d->ContentsLayout->isEmpty() && DockContainer->dockAreaCount() >= 1) {
        QX_DOCK_PRINT("Dock Area empty");
        DockContainer->removeDockArea(this);
        this->deleteLater();
        if (DockContainer->dockAreaCount() == 0) {
            if (DockFloatingContainer *FloatingDockContainer = DockContainer->floatingWidget()) {
                FloatingDockContainer->hide();
                FloatingDockContainer->deleteLater();
            }
        }
    } else if (dockWidget == CurrentDockWidget) {
        // if contents layout is not empty but there are no more open dock
        // widgets, then we need to hide the dock area because it does not
        // contain any visible content
        hideAreaWithNoVisibleContent();
    }

    d->updateTitleBarButtonStates();
    updateTitleBarVisibility();
    d->updateMinimumSizeHint();
    auto TopLevelDockWidget = DockContainer->topLevelDockWidget();
    if (TopLevelDockWidget) {
        TopLevelDockWidget->emitTopLevelChanged(true);
    }

#if (QX_DOCK_DEBUG_LEVEL > 0)
    DockContainer->dumpLayout();
#endif
}

void DockAreaWidget::hideAreaWithNoVisibleContent()
{
    this->toggleView(false);

    // Hide empty parent splitters
    auto Splitter = internal::findParent<DockSplitter *>(this);
    internal::hideEmptyParentSplitters(Splitter);

    // Hide empty floating widget
    DockContainerWidget *Container = this->dockContainer();
    if (!Container->isFloating() && !DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) {
        return;
    }

    updateTitleBarVisibility();
    auto TopLevelWidget = Container->topLevelDockWidget();
    auto FloatingWidget = Container->floatingWidget();
    if (TopLevelWidget) {
        if (FloatingWidget) {
            FloatingWidget->updateWindowTitle();
        }
        DockWidget::emitTopLevelEventForWidget(TopLevelWidget, true);
    } else if (Container->openedDockAreas().isEmpty() && FloatingWidget) {
        FloatingWidget->hide();
    }
    if (isAutoHide()) {
        autoHideDockContainer()->hide();
    }
}

void DockAreaWidget::onTabCloseRequested(int Index)
{
    QX_DOCK_PRINT("DockAreaWidget::onTabCloseRequested " << Index);
    auto *dockWidget = this->dockWidget(Index);
    if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) ||
        dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) {
        dockWidget->closeDockWidgetInternal();
    } else {
        dockWidget->toggleView(false);
    }
}

DockWidget *DockAreaWidget::currentDockWidget() const
{
    int CurrentIndex = currentIndex();
    if (CurrentIndex < 0) {
        return nullptr;
    }

    return dockWidget(CurrentIndex);
}

void DockAreaWidget::setCurrentDockWidget(DockWidget *dockWidget)
{
    if (dockManager()->isRestoringState()) {
        return;
    }

    internalSetCurrentDockWidget(dockWidget);
}

void DockAreaWidget::internalSetCurrentDockWidget(DockWidget *dockWidget)
{
    int Index = index(dockWidget);
    if (Index < 0) {
        return;
    }

    setCurrentIndex(Index);
    dockWidget->setClosedState(false);   // Set current index can show the widget without changing the close state,
                                         // added to keep the close state consistent
}

void DockAreaWidget::setCurrentIndex(int index)
{
    auto TabBar = d->tabBar();
    if (index < 0 || index > (TabBar->count() - 1)) {
        qWarning() << Q_FUNC_INFO << "Invalid index" << index;
        return;
    }

    auto cw = d->ContentsLayout->currentWidget();
    auto nw = d->ContentsLayout->widget(index);
    if (cw == nw && !nw->isHidden()) {
        return;
    }

    Q_EMIT currentChanging(index);
    TabBar->setCurrentIndex(index);
    d->ContentsLayout->setCurrentIndex(index);
    d->ContentsLayout->currentWidget()->show();
    Q_EMIT currentChanged(index);
}

int DockAreaWidget::currentIndex() const
{
    return d->ContentsLayout->currentIndex();
}

QRect DockAreaWidget::titleBarGeometry() const
{
    return d->TitleBar->geometry();
}

QRect DockAreaWidget::contentAreaGeometry() const
{
    return d->ContentsLayout->geometry();
}

int DockAreaWidget::index(DockWidget *dockWidget)
{
    return d->ContentsLayout->indexOf(dockWidget);
}

QList<DockWidget *> DockAreaWidget::dockWidgets() const
{
    QList<DockWidget *> DockWidgetList;
    for (int i = 0; i < d->ContentsLayout->count(); ++i) {
        DockWidgetList.append(dockWidget(i));
    }
    return DockWidgetList;
}

int DockAreaWidget::openDockWidgetsCount() const
{
    int Count = 0;
    for (int i = 0; i < d->ContentsLayout->count(); ++i) {
        if (!dockWidget(i)->isClosed()) {
            ++Count;
        }
    }
    return Count;
}

QList<DockWidget *> DockAreaWidget::openedDockWidgets() const
{
    QList<DockWidget *> DockWidgetList;
    for (int i = 0; i < d->ContentsLayout->count(); ++i) {
        DockWidget *dockWidget = this->dockWidget(i);
        if (!dockWidget->isClosed()) {
            DockWidgetList.append(this->dockWidget(i));
        }
    }
    return DockWidgetList;
}

int DockAreaWidget::indexOfFirstOpenDockWidget() const
{
    for (int i = 0; i < d->ContentsLayout->count(); ++i) {
        if (!dockWidget(i)->isClosed()) {
            return i;
        }
    }

    return -1;
}

int DockAreaWidget::dockWidgetsCount() const
{
    return d->ContentsLayout->count();
}

DockWidget *DockAreaWidget::dockWidget(int Index) const
{
    return qobject_cast<DockWidget *>(d->ContentsLayout->widget(Index));
}

void DockAreaWidget::reorderDockWidget(int fromIndex, int toIndex)
{
    QX_DOCK_PRINT("DockAreaWidget::reorderDockWidget");
    if (fromIndex >= d->ContentsLayout->count() || fromIndex < 0 || toIndex >= d->ContentsLayout->count() ||
        toIndex < 0 || fromIndex == toIndex) {
        QX_DOCK_PRINT("Invalid index for tab movement" << fromIndex << toIndex);
        return;
    }

    auto Widget = d->ContentsLayout->widget(fromIndex);
    d->ContentsLayout->removeWidget(Widget);
    d->ContentsLayout->insertWidget(toIndex, Widget);
    setCurrentIndex(toIndex);
}

void DockAreaWidget::toggleDockWidgetView(DockWidget *dockWidget, bool Open)
{
    Q_UNUSED(dockWidget);
    Q_UNUSED(Open);
    updateTitleBarVisibility();
}

void DockAreaWidget::updateTitleBarVisibility()
{
    DockContainerWidget *Container = dockContainer();
    if (!Container) {
        return;
    }

    if (!d->TitleBar) {
        return;
    }

    bool IsAutoHide = isAutoHide();
    if (!DockManager::testConfigFlag(DockManager::AlwaysShowTabs)) {
        bool Hidden =
            Container->hasTopLevelDockWidget() &&
            (Container->isFloating() || DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar));
        Hidden |= (d->Flags.testFlag(HideSingleWidgetTitleBar) && openDockWidgetsCount() == 1);
        Hidden &= !IsAutoHide;   // Titlebar must always be visible when auto hidden so it can be dragged
        d->TitleBar->setVisible(!Hidden);
    }

    if (isAutoHideFeatureEnabled()) {
        auto tabBar = d->TitleBar->tabBar();
        tabBar->setVisible(!IsAutoHide);                             // Never show tab bar when auto hidden
        d->TitleBar->autoHideTitleLabel()->setVisible(IsAutoHide);   // Always show when auto hidden, never otherwise
        updateTitleBarButtonVisibility(Container->topLevelDockArea() == this);
    }
}

void DockAreaWidget::markTitleBarMenuOutdated()
{
    if (d->TitleBar) {
        d->TitleBar->markTabsMenuOutdated();
    }
}

void DockAreaWidget::updateAutoHideButtonCheckState()
{
    auto autoHideButton = titleBarButton(TitleBarButtonAutoHide);
    autoHideButton->blockSignals(true);
    autoHideButton->setChecked(isAutoHide());
    autoHideButton->blockSignals(false);
}

void DockAreaWidget::updateTitleBarButtonVisibility(bool IsTopLevel) const
{
    d->updateTitleBarButtonVisibility(IsTopLevel);
}

void DockAreaWidget::updateTitleBarButtonsToolTips()
{
    internal::setToolTip(titleBarButton(TitleBarButtonClose), titleBar()->titleBarButtonToolTip(TitleBarButtonClose));
    internal::setToolTip(titleBarButton(TitleBarButtonAutoHide),
                         titleBar()->titleBarButtonToolTip(TitleBarButtonAutoHide));
}

void DockAreaWidget::saveState(QXmlStreamWriter &s) const
{
    s.writeStartElement("Area");
    s.writeAttribute("Tabs", QString::number(d->ContentsLayout->count()));
    auto CurrentDockWidget = currentDockWidget();
    QString Name = CurrentDockWidget ? CurrentDockWidget->objectName() : "";
    s.writeAttribute("Current", Name);

    if (d->AllowedAreas != DefaultAllowedAreas) {
        s.writeAttribute("AllowedAreas", QString::number(d->AllowedAreas, 16));
    }

    if (d->Flags != DefaultFlags) {
        s.writeAttribute("Flags", QString::number(d->Flags, 16));
    }
    QX_DOCK_PRINT("DockAreaWidget::saveState TabCount: " << d->ContentsLayout->count() << " Current: " << Name);
    for (int i = 0; i < d->ContentsLayout->count(); ++i) {
        dockWidget(i)->saveState(s);
    }
    s.writeEndElement();
}

bool DockAreaWidget::restoreState(DockStateReader &s, DockAreaWidget *&CreatedWidget, bool Testing,
                                   DockContainerWidget *Container)
{
    bool Ok;
#ifdef QX_DOCK_DEBUG_PRINT
    int Tabs = s.attributes().value("Tabs").toInt(&Ok);
    if (!Ok) {
        return false;
    }
#endif

    QString CurrentDockWidget = s.attributes().value("Current").toString();
    QX_DOCK_PRINT("Restore NodeDockArea Tabs: " << Tabs << " Current: " << CurrentDockWidget);

    auto dockManager = Container->dockManager();
    DockAreaWidget *dockArea = nullptr;
    if (!Testing) {
        dockArea = new DockAreaWidget(dockManager, Container);
        const auto AllowedAreasAttribute = s.attributes().value("AllowedAreas");
        if (!AllowedAreasAttribute.isEmpty()) {
            dockArea->setAllowedAreas((DockWidgetArea)AllowedAreasAttribute.toInt(nullptr, 16));
        }

        const auto FlagsAttribute = s.attributes().value("Flags");
        if (!FlagsAttribute.isEmpty()) {
            dockArea->setDockAreaFlags((DockAreaWidget::DockAreaFlags)FlagsAttribute.toInt(nullptr, 16));
        }
    }

    while (s.readNextStartElement()) {
        if (s.name() != QLatin1String("Widget")) {
            continue;
        }

        auto ObjectName = s.attributes().value("Name");
        if (ObjectName.isEmpty()) {
            return false;
        }

        bool Closed = s.attributes().value("Closed").toInt(&Ok);
        if (!Ok) {
            return false;
        }

        s.skipCurrentElement();
        DockWidget *dockWidget = dockManager->findDockWidget(ObjectName.toString());
        if (!dockWidget || Testing) {
            continue;
        }

        QX_DOCK_PRINT("Dock Widget found - parent " << dockWidget->parent());
        if (dockWidget->autoHideDockContainer()) {
            dockWidget->autoHideDockContainer()->cleanupAndDelete();
        }

        // We hide the dockArea here to prevent the short display (the flashing)
        // of the dock areas during application startup
        dockArea->hide();
        dockArea->addDockWidget(dockWidget);
        dockWidget->setToggleViewActionChecked(!Closed);
        dockWidget->setClosedState(Closed);
        dockWidget->setProperty(internal::ClosedProperty, Closed);
        dockWidget->setProperty(internal::DirtyProperty, false);
    }

    if (Testing) {
        return true;
    }

    if (!dockArea->dockWidgetsCount()) {
        delete dockArea;
        dockArea = nullptr;
    } else {
        dockArea->setProperty("currentDockWidget", CurrentDockWidget);
    }

    CreatedWidget = dockArea;
    return true;
}

DockWidget *DockAreaWidget::nextOpenDockWidget(DockWidget *dockWidget) const
{
    auto OpenDockWidgets = openedDockWidgets();
    if (OpenDockWidgets.count() > 1 || (OpenDockWidgets.count() == 1 && OpenDockWidgets[0] != dockWidget)) {
        if (OpenDockWidgets.last() == dockWidget) {
            DockWidget *NextDockWidget = OpenDockWidgets[OpenDockWidgets.count() - 2];
            // search backwards for widget with tab
            for (int i = OpenDockWidgets.count() - 2; i >= 0; --i) {
                auto dw = OpenDockWidgets[i];
                if (!dw->features().testFlag(DockWidget::NoTab)) {
                    return dw;
                }
            }

            // return widget without tab
            return NextDockWidget;
        } else {
            int IndexOfDockWidget = OpenDockWidgets.indexOf(dockWidget);
            DockWidget *NextDockWidget = OpenDockWidgets[IndexOfDockWidget + 1];
            // search forwards for widget with tab
            for (int i = IndexOfDockWidget + 1; i < OpenDockWidgets.count(); ++i) {
                auto dw = OpenDockWidgets[i];
                if (!dw->features().testFlag(DockWidget::NoTab)) {
                    return dw;
                }
            }

            // search backwards for widget with tab
            for (int i = IndexOfDockWidget - 1; i >= 0; --i) {
                auto dw = OpenDockWidgets[i];
                if (!dw->features().testFlag(DockWidget::NoTab)) {
                    return dw;
                }
            }

            // return widget without tab
            return NextDockWidget;
        }
    } else {
        return nullptr;
    }
}

DockWidget::DockWidgetFeatures DockAreaWidget::features(eBitwiseOperator Mode) const
{
    if (BitwiseAnd == Mode) {
        DockWidget::DockWidgetFeatures Features(DockWidget::AllDockWidgetFeatures);
        for (const auto dockWidget : dockWidgets()) {
            Features &= dockWidget->features();
        }
        return Features;
    } else {
        DockWidget::DockWidgetFeatures Features(DockWidget::NoDockWidgetFeatures);
        for (const auto dockWidget : dockWidgets()) {
            Features |= dockWidget->features();
        }
        return Features;
    }
}

void DockAreaWidget::toggleView(bool Open)
{
    setVisible(Open);

    Q_EMIT viewToggled(Open);
}

void DockAreaWidget::setVisible(bool Visible)
{
    Super::setVisible(Visible);
    if (d->UpdateTitleBarButtons) {
        d->updateTitleBarButtonStates();
    }
}

void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas)
{
    d->AllowedAreas = areas;
}

DockWidgetAreas DockAreaWidget::allowedAreas() const
{
    return d->AllowedAreas;
}

DockAreaWidget::DockAreaFlags DockAreaWidget::dockAreaFlags() const
{
    return d->Flags;
}

void DockAreaWidget::setDockAreaFlags(DockAreaFlags Flags)
{
    auto ChangedFlags = d->Flags ^ Flags;
    d->Flags = Flags;
    if (ChangedFlags.testFlag(HideSingleWidgetTitleBar)) {
        updateTitleBarVisibility();
    }
}

void DockAreaWidget::setDockAreaFlag(eDockAreaFlag Flag, bool On)
{
    auto flags = dockAreaFlags();
    internal::setFlag(flags, Flag, On);
    setDockAreaFlags(flags);
}

QAbstractButton *DockAreaWidget::titleBarButton(TitleBarButton which) const
{
    return d->TitleBar->button(which);
}

void DockAreaWidget::closeArea()
{
    // If there is only one single dock widget and this widget has the
    // DeleteOnClose feature or CustomCloseHandling, then we delete the dock widget now;
    // in the case of CustomCloseHandling, the DockWidget class will emit its
    // closeRequested signal and not actually delete unless the signal is handled in a way that deletes it
    auto OpenDockWidgets = openedDockWidgets();
    if (OpenDockWidgets.count() == 1 &&
        (OpenDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose) ||
         OpenDockWidgets[0]->features().testFlag(DockWidget::CustomCloseHandling)) &&
        !isAutoHide()) {
        OpenDockWidgets[0]->closeDockWidgetInternal();
    } else {
        for (auto dockWidget : openedDockWidgets()) {
            if ((dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) &&
                 dockWidget->features().testFlag(DockWidget::DockWidgetForceCloseWithArea)) ||
                dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) {
                dockWidget->closeDockWidgetInternal();
            } else if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) && isAutoHide()) {
                dockWidget->closeDockWidgetInternal();
            } else {
                dockWidget->toggleView(false);
            }
        }
    }
}

enum eBorderLocation {
    BorderNone = 0,
    BorderLeft = 0x01,
    BorderRight = 0x02,
    BorderTop = 0x04,
    BorderBottom = 0x08,
    BorderVertical = BorderLeft | BorderRight,
    BorderHorizontal = BorderTop | BorderBottom,
    BorderTopLeft = BorderTop | BorderLeft,
    BorderTopRight = BorderTop | BorderRight,
    BorderBottomLeft = BorderBottom | BorderLeft,
    BorderBottomRight = BorderBottom | BorderRight,
    BorderVerticalBottom = BorderVertical | BorderBottom,
    BorderVerticalTop = BorderVertical | BorderTop,
    BorderHorizontalLeft = BorderHorizontal | BorderLeft,
    BorderHorizontalRight = BorderHorizontal | BorderRight,
    BorderAll = BorderVertical | BorderHorizontal
};

SideBarLocation DockAreaWidget::calculateSideTabBarArea() const
{
    auto Container = dockContainer();
    auto ContentRect = Container->contentRect();

    int borders = BorderNone;   // contains all borders that are touched by the dock ware
    auto DockAreaTopLeft = mapTo(Container, rect().topLeft());
    auto DockAreaRect = rect();
    DockAreaRect.moveTo(DockAreaTopLeft);
    const qreal aspectRatio = DockAreaRect.width() / (qMax(1, DockAreaRect.height()) * 1.0);
    const qreal sizeRatio = (qreal)ContentRect.width() / DockAreaRect.width();
    static const int MinBorderDistance = 16;
    bool HorizontalOrientation = (aspectRatio > 1.0) && (sizeRatio < 3.0);

    // measure border distances - a distance less than 16 px means we touch the
    // border
    int BorderDistance[4];

    int Distance = qAbs(ContentRect.topLeft().y() - DockAreaRect.topLeft().y());
    BorderDistance[SideBarLocation::SideBarTop] = (Distance < MinBorderDistance) ? 0 : Distance;
    if (!BorderDistance[SideBarLocation::SideBarTop]) {
        borders |= BorderTop;
    }

    Distance = qAbs(ContentRect.bottomRight().y() - DockAreaRect.bottomRight().y());
    BorderDistance[SideBarLocation::SideBarBottom] = (Distance < MinBorderDistance) ? 0 : Distance;
    if (!BorderDistance[SideBarLocation::SideBarBottom]) {
        borders |= BorderBottom;
    }

    Distance = qAbs(ContentRect.topLeft().x() - DockAreaRect.topLeft().x());
    BorderDistance[SideBarLocation::SideBarLeft] = (Distance < MinBorderDistance) ? 0 : Distance;
    if (!BorderDistance[SideBarLocation::SideBarLeft]) {
        borders |= BorderLeft;
    }

    Distance = qAbs(ContentRect.bottomRight().x() - DockAreaRect.bottomRight().x());
    BorderDistance[SideBarLocation::SideBarRight] = (Distance < MinBorderDistance) ? 0 : Distance;
    if (!BorderDistance[SideBarLocation::SideBarRight]) {
        borders |= BorderRight;
    }

    auto SideTab = SideBarLocation::SideBarRight;
    switch (borders) {
    // 1. It's touching all borders
    case BorderAll:
        SideTab = HorizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarRight;
        break;

    // 2. It's touching 3 borders
    case BorderVerticalBottom:
        SideTab = SideBarLocation::SideBarBottom;
        break;
    case BorderVerticalTop:
        SideTab = SideBarLocation::SideBarTop;
        break;
    case BorderHorizontalLeft:
        SideTab = SideBarLocation::SideBarLeft;
        break;
    case BorderHorizontalRight:
        SideTab = SideBarLocation::SideBarRight;
        break;

    // 3. Its touching horizontal or vertical borders
    case BorderVertical:
        SideTab = SideBarLocation::SideBarBottom;
        break;
    case BorderHorizontal:
        SideTab = SideBarLocation::SideBarRight;
        break;

    // 4. Its in a corner
    case BorderTopLeft:
        SideTab = HorizontalOrientation ? SideBarLocation::SideBarTop : SideBarLocation::SideBarLeft;
        break;
    case BorderTopRight:
        SideTab = HorizontalOrientation ? SideBarLocation::SideBarTop : SideBarLocation::SideBarRight;
        break;
    case BorderBottomLeft:
        SideTab = HorizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarLeft;
        break;
    case BorderBottomRight:
        SideTab = HorizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarRight;
        break;

    // 5 Ists touching only one border
    case BorderLeft:
        SideTab = SideBarLocation::SideBarLeft;
        break;
    case BorderRight:
        SideTab = SideBarLocation::SideBarRight;
        break;
    case BorderTop:
        SideTab = SideBarLocation::SideBarTop;
        break;
    case BorderBottom:
        SideTab = SideBarLocation::SideBarBottom;
        break;
    }

    return SideTab;
}

void DockAreaWidget::setAutoHide(bool Enable, SideBarLocation Location)
{
    if (!isAutoHideFeatureEnabled()) {
        return;
    }

    if (!Enable) {
        if (isAutoHide()) {
            autoHideDockContainer()->moveContentsToParent();
        }
        return;
    }

    auto area = (SideBarNone == Location) ? calculateSideTabBarArea() : Location;
    for (const auto dockWidget : openedDockWidgets()) {
        if (Enable == isAutoHide()) {
            continue;
        }

        if (!dockWidget->features().testFlag(DockWidget::DockWidgetPinnable)) {
            continue;
        }

        dockContainer()->createAndSetupAutoHideContainer(area, dockWidget);
    }
}

void DockAreaWidget::toggleAutoHide(SideBarLocation Location)
{
    if (!isAutoHideFeatureEnabled()) {
        return;
    }

    setAutoHide(!isAutoHide(), Location);
}

void DockAreaWidget::closeOtherAreas()
{
    dockContainer()->closeOtherAreas(this);
}

DockAreaTitleBar *DockAreaWidget::titleBar() const
{
    return d->TitleBar;
}

bool DockAreaWidget::isCentralWidgetArea() const
{
    if (dockWidgetsCount() != 1) {
        return false;
    }

    return dockManager()->centralWidget() == dockWidgets().constFirst();
}

bool DockAreaWidget::containsCentralWidget() const
{
    auto centralWidget = dockManager()->centralWidget();
    for (const auto &dockWidget : dockWidgets()) {
        if (dockWidget == centralWidget) {
            return true;
        }
    }

    return false;
}

QSize DockAreaWidget::minimumSizeHint() const
{
    if (!d->MinSizeHint.isValid()) {
        return Super::minimumSizeHint();
    }

    if (d->TitleBar->isVisible()) {
        return d->MinSizeHint + QSize(0, d->TitleBar->minimumSizeHint().height());
    } else {
        return d->MinSizeHint;
    }
}

void DockAreaWidget::onDockWidgetFeaturesChanged()
{
    if (d->TitleBar) {
        d->updateTitleBarButtonStates();
    }
}

bool DockAreaWidget::isTopLevelArea() const
{
    auto Container = dockContainer();
    if (!Container) {
        return false;
    }

    return (Container->topLevelDockArea() == this);
}

#ifdef Q_OS_WIN

bool DockAreaWidget::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::PlatformSurface:
        return true;
    default:
        break;
    }

    return Super::event(e);
}
#endif

QX_END_NAMESPACE
